IAM ポリシーシミュレータ API を使用して、特定のアクションを許可しているかつ許可していないIAMロール、ユーザーを抽出してみた

IAM ポリシーシミュレータ API を使用して、特定のアクションを許可しているかつ許可していないIAMロール、ユーザーを抽出してみた

Clock Icon2024.07.05

こんにちは、AWS事業本部 梶原@福岡オフィス です。今回は、AWS IAM ロール、ユーザーの権限を詳細に分析するスクリプトを作成し、特定の条件を満たすロール、ユーザーを抽出する方法について共有したいと思います。

*※注意(2024/07/08 追記)
IAM ポリシーシミュレータ API の制限により、Organizations環境のSCP制限可(リージョン制限など)の場合、正確に権限が取得できず、対象が抽出できないことがあります。
*

背景

あるアクションは許可しつつ、別のアクションは明示的に拒否したいという要件はよくありますが、今回は、Lambda 関数に関する以下の条件を満たすロールを抽出することにしました:

  • lambda:GetFunction アクションを許可している
  • lambda:ListTags アクションを許可していない

アプローチ

この課題に取り組むため、生成AIとしてAWS Bedrockで提供されているAnthropicのClaude 3.5 Sonnetを使用しつつ、以下のPythonスクリプトを作成しました:

  1. すべての IAM ロール、IAMユーザーをリストアップ
  2. 各ロール、ユーザーに対して、指定したアクションの権限をシミュレートして、権限を確認する
  3. 条件を満たすロール、ユーザーを抽出する
  4. 結果、進捗を分かりやすく表示

コード

以下生成AIを用いて生成し、私の方で編集、最終的に作成したPythonスクリプトのコードになります

※2024/07/08 ループ処理時にスリープ処理を入れました Thank you for your feedback @hato

import boto3
import time ## 追加
from botocore.exceptions import ClientError
import sys

def simulate_policy(arn, actions):
    iam = boto3.client('iam')
    try:
        response = iam.simulate_principal_policy(
            PolicySourceArn=arn,
            ActionNames=actions
        )
        return {action: any(result['EvalDecision'] == 'allowed' for result in response['EvaluationResults'] if result['EvalActionName'] == action)
                for action in actions}
    except ClientError as e:
        print(f"Error simulating policy for entity {arn}: {e}")
        return {action: False for action in actions}

def get_entities_with_specific_access():
    iam = boto3.client('iam')
    entities_with_specific_access = []
    all_entities_info = []
    processed_entities = 0

    # ロールの処理
    role_paginator = iam.get_paginator('list_roles')
    for page in role_paginator.paginate():
        for role in page['Roles']:
            time.sleep(0.1) ## 追加
            processed_entities += 1
            role_name = role['RoleName']
            role_arn = role['Arn']

            sys.stdout.write(f"\rProcessed entities: {processed_entities}, Current: Role - {role_name}")
            sys.stdout.flush()

            simulation_results = simulate_policy(role_arn, ['lambda:GetFunction', 'lambda:ListTags'])

            entity_info = {
                'type': 'Role',
                'name': role_name,
                'arn': role_arn,
                'get_function': simulation_results.get('lambda:GetFunction', False),
                'list_tags': simulation_results.get('lambda:ListTags', False)
            }
            all_entities_info.append(entity_info)

            if entity_info['get_function'] and not entity_info['list_tags']:
                entities_with_specific_access.append((role_name, 'Role'))

    # ユーザーの処理
    user_paginator = iam.get_paginator('list_users')
    for page in user_paginator.paginate():
        for user in page['Users']:
            time.sleep(0.1)   ## 追加
            processed_entities += 1
            user_name = user['UserName']
            user_arn = user['Arn']

            sys.stdout.write(f"\rProcessed entities: {processed_entities}, Current: User - {user_name}")
            sys.stdout.flush()

            simulation_results = simulate_policy(user_arn, ['lambda:GetFunction', 'lambda:ListTags'])

            entity_info = {
                'type': 'User',
                'name': user_name,
                'arn': user_arn,
                'get_function': simulation_results.get('lambda:GetFunction', False),
                'list_tags': simulation_results.get('lambda:ListTags', False)
            }
            all_entities_info.append(entity_info)

            if entity_info['get_function'] and not entity_info['list_tags']:
                entities_with_specific_access.append((user_name, 'User'))

    print()  # 進捗表示の後に改行
    return entities_with_specific_access, processed_entities, all_entities_info

# メイン処理
try:
    print("Analyzing IAM roles and users...")
    entities, total_processed, all_entities_info = get_entities_with_specific_access()

    print(f"\nTotal entities processed: {total_processed}")
    print("Entities with access to GetFunction but not to ListTags:")
    for entity, entity_type in entities:
        print(f"{entity_type}: {entity}")
    print(f"\nTotal entities meeting criteria: {len(entities)}")

    # 詳細情報の表示
    print("\nDetailed information:")
    for entity_info in all_entities_info:
        print(f"{entity_info['type']}: {entity_info['name']}")
        print(f"  ARN: {entity_info['arn']}")
        print(f"  GetFunction: {'Allowed' if entity_info['get_function'] else 'Denied'}")
        print(f"  ListTags: {'Allowed' if entity_info['list_tags'] else 'Denied'}")
        print()

except Exception as e:
    print(f"An unexpected error occurred: {e}")

やってみた

CloudShell に上記スクリプトをアップロードして実行してみました。
なお、待機処理などをいれていないので、環境によってはRoleが大量にありAPI Rate等で引っかかる可能性があるかとおもいますので、その場合は時間を置く、待機処理を入れるなどの工夫をしてください。

100件程度では大丈夫そうでしたが、200件を超えるあたりからRate Errorが出ることが多くなります。

$ python get_roles_with_specific_access.py 
Analyzing IAM roles...  
Processed roles: 195, Current role: TestCfnResource-role-xxxxx

Total roles processed: 195

以下 3件のロールが抽出されていました

Roles with access to GetFunction but not to ListTags:
AWSServiceRoleForAmazonInspector2
AWSServiceRoleForSupport
LambdaGetFunctionOnlyRole

Total roles meeting criteria: 3

なお、出力詳細結果に各ロールでチェック詳細が出力されます

Detailed information:
Role: xxxxx
  GetFunction: Denied
  ListTags: Denied

どちらの権限も不許可

Role: xxxxx
  GetFunction: Allowed
  ListTags: Allowed

どちらの権限も許可

Role: LambdaGetFunctionOnlyRole
  GetFunction: Allowed
  ListTags: Denied

GetFunction のみ許可。これだ!

対象のロールにListTagsをつけてみる

いくつかロールが検出されたので、確認します。

AWSServiceRoleForAmazonInspector2

私の環境ではAWS Inspectorに設定しているサービスのロールが検出されていました。
こちらで、該当するポリシーはAmazonInspector2ServiceRolePolicyとなりAWS管理のロールの為、対応は保留しました。 対応不要です。

※2024/07/08 追記 サービスリンクロールについてAWS 管理となり対応不要の旨、追記しました。

【小ネタ】サービスリンクロールとサービスロールを見分ける方法 | DevelopersIO
https://dev.classmethod.jp/articles/iam-how-to-identify-service-linked-roles/

AWSServiceRoleForSupport

私の環境ではAWSサポートに設定しているサービスのロールが検出されていました。
こちらで、該当するポリシーはAWSSupportServiceRolePolicyとなりAWS管理のロールの為、対応は対応は保留しました。 対応不要です。

※2024/07/08 追記 サービスリンクロールについてAWS 管理となり対応不要の旨、追記しました。

検出されたロール

たしかにInline Policyで以下のGetFunctionのみの付与でした

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "lambda:GetFunction",
            "Resource": "*"
        }
    ]
}

以下のようにポリシーの修正を行い。lambda:ListTags の付与を行いました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "lambda:ListTags",
                "lambda:GetFunction"
            ],
            "Resource": "*"
        }
    ]
}

まとめ

IAM ロールの権限分析は、AWS 環境のセキュリティを維持する上で非常に重要かと思います。
あまり、全ロールを積極的に調べることはないかと思いますが。特定のAPIが許可されているロールを探したいというときは割とあるので、お役にたてると幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.